#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <sys/ioctl.h>
#include <sys/mman.h>
#include <linux/fb.h>
#include <string.h>
//bmp 占用空间大 没有失真 图像简单
#define argb8888_to_rgb565(color) ({ \
 unsigned int temp = (color); \
 ((temp & 0xF80000UL) >> 8) | \
 ((temp & 0xFC00UL) >> 5) | \
 ((temp & 0xF8UL) >> 3); \
 })
//四部分组成：文件头/位图信息头/调色板/位图数据
// 文件头：文件的大小/格式/到位图数据的偏移量
// 信息头：信息头大小 /图像尺寸/图像大小/位平面数
// 调色板：索引与颜色的映射表
// 位图数据：图像数据
// 定义了很多结构体 结构体里面有用到的数据
// 在bmp图片中 如果通过16进制文件打开 他的头部就是这个文件的一些信息
// 提取有用的然后转换过来

//文件头数据结构、
// 大小是14个字节 （当然结构体不一定只占用14个）
typedef struct {
    unsigned char type[2]; // 文件的类型 BM / BA / CT
    unsigned  int size; //文件的大小
    unsigned short reserved1; //保留字段 1
    unsigned short reserved2; //保留字段
    unsigned int offset; //到位图数据的偏移量 2
}    __attribute__ ((packed)) bmp_file_header; //等会看看啥意思奥
// 这个意思就是不要进行内存对齐 这样的话才能刚好read 14 字节和这个对得上
/**** 位图信息头数据结构 ****/
// 假如编码格式不是RGB 而是BF 就会多出来一个32byte长度的掩码
typedef struct {
    unsigned int size; //位图信息头大小
    int width; //图像宽度
    int height; //图像高度（int 是因为有可能是倒向位图）
    unsigned short planes; //位面数
    unsigned short bpp; //像素深度
    unsigned int compression; //压缩方式
    unsigned int image_size; //图像大小
    int x_pels_per_meter; //像素/米
    int y_pels_per_meter; //像素/米
    unsigned int clr_used;
    unsigned int clr_omportant;
} __attribute__ ((packed)) bmp_info_header;


/**** 静态全局变量 ****/
static int width; //LCD X 分辨率
static int height; //LCD Y 分辨率
static unsigned short *screen_base = NULL; //映射后的显存基地址
static unsigned long line_length; //LCD 一行的长度（字节为单位）

/********************************************************************
* 函数名称： show_bmp_image
* 功能描述： 在 LCD 上显示指定的 BMP 图片
* 输入参数： 文件路径
* 返 回 值： 成功返回 0, 失败返回-1
********************************************************************/
void to565Img( unsigned short * line_buf, unsigned long line_bytes,unsigned short bpp);

static int show_bmp_image(const char *path)
{
    bmp_file_header file_h;
    bmp_info_header info_h;
    unsigned short *line_buf = NULL;//行缓冲区
    unsigned long line_bytes;   //BMP图像一行字节的大小
    unsigned int min_h, min_bytes;
    int fd = -1;
    int j;
    /* 打开文件 */
    if(0 > (fd = open(path, O_RDONLY))) {
        perror("open error");
        return -1;
    }
    if(sizeof(bmp_file_header) != read(fd,&file_h,sizeof(bmp_file_header))) {
        perror("read error");
        close(fd);
        return -1;
    }
    if(0 != memcmp(file_h.type,"BM",2)) {
        fprintf(stderr, "it's not a BMP file\n");
        close(fd);
        return -1;
    }
    /* 读取信息头 */ //read也偏移到位了
    /* 读取位图信息头 */
    if (sizeof(bmp_info_header) !=
        read(fd, &info_h, sizeof(bmp_info_header))) {
        perror("read error");
        close(fd);
        return -1;
    }
    /* 打印信息 */
    printf("文件大小: %d\n"
    "位图数据的偏移量: %d\n"
    "位图信息头大小: %d\n"
    "图像分辨率: %d*%d\n"
    "像素深度: %d\n", file_h.size, file_h.offset,
    info_h.size, info_h.width, info_h.height,
    info_h.bpp);
    // 找到图像开始的位置(位图数据) lseek过去
    if(-1 == lseek(fd,file_h.offset,SEEK_SET)) {
        perror("seek error");
        close(fd);
        return -1;
    }
    /*申请一个buf,暂存bmp图像一行的数据*/
    line_bytes = info_h.width * info_h.bpp / 8;
    line_buf = malloc(line_bytes);
    if (NULL == line_buf) {
        fprintf(stderr, "malloc error\n");
        close(fd);
        return -1;
    }
    // 就意思是啥呢 图像的大小和屏幕显示的大小取个最小

    if(line_length > line_bytes)
        min_bytes = line_bytes;
    else
        min_bytes = line_length;
    
    // 这里可以判断565还是888 做个转换
    // 或者也可能是ARGB 带着透明图
    // 整数就是倒向位图
    if(0 < info_h.height) {
        // 显示不全 那头部就不惜要了
        if(info_h.height > height) {
            min_h = height;
            // 不要头部了
            lseek(fd,(info_h.height - height) * line_bytes,SEEK_CUR);
            screen_base += width * (height - 1); //定位到屏幕左下角位置
        }
        else {
            min_h = info_h.height;
            screen_base += width * (info_h.height - 1); //定位到....不知怎么描述 懂的人自然懂!
        }
        for(j = min_h;j > 0;screen_base -= width,j--) {

            read(fd, line_buf, line_bytes); //读取出图像数据
            to565Img(line_buf,line_bytes,info_h.bpp);
            // 这里可以多个函数 假如是888 对这个进行一个转换
            memcpy(screen_base, line_buf, min_bytes);//刷入 LCD 显存
        }
    }
    else { //正向位图
        int temp = 0 - info_h.height; //负数转成正数
        if (temp > height)
            min_h = height;
        else
            min_h = temp;
        for (j = 0; j < min_h; j++, screen_base += width) {
            read(fd, line_buf, line_bytes);
            to565Img(line_buf,line_bytes,info_h.bpp);
            memcpy(screen_base, line_buf, min_bytes);
            }
    }
    /* 关闭文件、函数返回 */
    close(fd);
    free(line_buf);
    return 0;
}
void to565Img( unsigned short * line_buf, unsigned long line_bytes,unsigned short bpp)
{
    if(bpp == 24) {
        printf("需要转换\r\n");
        for(int i = 0; i < line_bytes;i++) {
            line_buf[i] = argb8888_to_rgb565(line_buf[i]);
        }
    }
}
int main(int argc, char *argv[])
{
    struct fb_fix_screeninfo fb_fix;
    struct fb_var_screeninfo fb_var;
    unsigned int screen_size;
    int fd;
    /* 传参校验 */
    if (2 != argc) {
        fprintf(stderr, "usage: %s <bmp_file>\n", argv[0]);
        exit(-1);
    }
    /* 打开 framebuffer 设备 */
    if (0 > (fd = open("/dev/fb0", O_RDWR))) {
        perror("open error");
        exit(EXIT_FAILURE);
    }
    /* 获取参数信息 */
    ioctl(fd, FBIOGET_VSCREENINFO, &fb_var);
    ioctl(fd, FBIOGET_FSCREENINFO, &fb_fix);
    screen_size = fb_fix.line_length * fb_var.yres;
    line_length = fb_fix.line_length;
    width = fb_var.xres;
    height = fb_var.yres;
    /* 将显示缓冲区映射到进程地址空间 */
    screen_base = mmap(NULL, screen_size, PROT_WRITE, MAP_SHARED, fd, 0);
    if (MAP_FAILED == (void *)screen_base) {
        perror("mmap error");
        close(fd);
        exit(EXIT_FAILURE);
    }
    /* 显示 BMP 图片 */
    memset(screen_base, 0xFF, screen_size);
    show_bmp_image(argv[1]);
    /* 退出 */
    munmap(screen_base, screen_size); //取消映射
    close(fd); //关闭文件
    exit(EXIT_SUCCESS); //退出进程
}